package com.hilinos.cloud.gateway.handler;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import com.hilinos.base.user.entity.vo.UserLoginVo;
import com.hilinos.cloud.gateway.common.conf.ServerConfig;
import com.hilinos.common.common.ReturnCode;
import com.hilinos.common.entity.RespVo;
import com.hilinos.common.util.EntityUtils;
import com.hilinos.common.util.MapUtils;
import com.hilinos.common.util.RequestUtils;
import com.hilinos.common.util.TypeReference;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private ServerConfig serverConfig;
    private static final Map<String, AtomicInteger> INSTANCE_INDEX = new ConcurrentHashMap<>();
    private static final String HK_TOKEN = "token";

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    public void setServerConfig(ServerConfig serverConfig) {
        this.serverConfig = serverConfig;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();

            String uri = getUri(request);

            // 不需要token认证
            if (isNoTokenUrl(uri)) {
                return chain.filter(exchange);
            }

            List<String> tokens = request.getHeaders().get(HK_TOKEN);

            if (ObjectUtils.isEmpty(tokens) || ObjectUtils.isEmpty(tokens.get(0))) {
                return returnAuthError(response);
            }

            String token = tokens.get(0);

            ServiceInstance instance = getDiscoveryClient(serverConfig.getTokenCheckInstanceId());
            if (ObjectUtils.isEmpty(instance)) {
                return returnNoInstance(response);
            }
            String httpResult = RequestUtils.postForm(
                    "http://" + instance.getHost() + ":" + instance.getPort()
                            + serverConfig.getTokenInsideCheckInstanceUri(),
                    MapUtils.getInstanceStringString()
                            .put(HK_TOKEN, token)
                            .build(),
                    null);

            RespVo<UserLoginVo> respVo = EntityUtils.jsonStr2Entity(httpResult,
                    new TypeReference<RespVo<UserLoginVo>>() {
                    });

            if (ObjectUtils.isEmpty(respVo)) {
                return returnAuthError(response);
            }

            if (serverConfig.getTokenOutsideCheckInstanceUri().equals(uri)) {
                response.setStatusCode(HttpStatus.OK);
                response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
                return response.writeWith(Mono.just(
                        response.bufferFactory().wrap(EntityUtils.toJsonString(respVo).getBytes(StandardCharsets.UTF_8))));
            }

            if (respVo.getCode() == 1) {
                request.mutate().header("appid", respVo.getData().getUserInfo().getAppid());
                return chain.filter(exchange);
            } else {
                return returnAuthError(response);
            }
        } catch (Exception e) {
            log.warn("接口请求失败 ", e);
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
            return response.writeWith(Mono.just(response.bufferFactory()
                    .wrap(EntityUtils.toJsonString(ReturnCode.NORMAL_SYSTEM_ERROR.resp()).getBytes(StandardCharsets.UTF_8))));
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private String getUri(ServerHttpRequest request) {
        String uri = request.getURI().getPath();
        StringBuilder sb = new StringBuilder();
        for (String s : uri.split("/")) {
            if (!ObjectUtils.isEmpty(s)) {
                sb.append("/").append(s);
            }
        }
        return sb.toString();
    }

    private boolean isNoTokenUrl(String uri) {
        if (uri == null) {
            return false;
        }
        for (String noTokenUri : serverConfig.getNoTokenUris()) {
            if (noTokenUri.equals(uri)) {
                return true;
            }
        }
        return false;
    }

    public Mono<Void> returnAuthError(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
        return response.writeWith(Mono.just(response.bufferFactory()
                .wrap(EntityUtils.toJsonString(ReturnCode.AUTH_ERROR.resp()).getBytes(StandardCharsets.UTF_8))));
    }

    public Mono<Void> returnNoInstance(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
        return response.writeWith(Mono.just(response.bufferFactory()
                .wrap(EntityUtils.toJsonString(ReturnCode.SERVER_ERROR_OFFLINE.resp()).getBytes(StandardCharsets.UTF_8))));
    }

    public Mono<Void> returnNoAppid(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
        return response.writeWith(Mono.just(response.bufferFactory()
                .wrap(EntityUtils.toJsonString(ReturnCode.NORMAL_PARAM_LOST_ERROR.respMsg("请求头必须包含appid"))
                        .getBytes(StandardCharsets.UTF_8))));
    }

    public ServiceInstance getDiscoveryClient(String instanceId) {
        instanceId = instanceId.toUpperCase();
        int index;
        synchronized (INSTANCE_INDEX) {
            INSTANCE_INDEX.putIfAbsent(instanceId, new AtomicInteger());
            index = INSTANCE_INDEX.get(instanceId).addAndGet(1);
            if (index == Integer.MAX_VALUE - 1) {
                INSTANCE_INDEX.get(instanceId).set(0);
            }
        }
        List<ServiceInstance> list = discoveryClient.getInstances(instanceId);
        if (list == null || list.size() == 0) {
            return null;
        }
        return list.get(index % list.size());
    }

}
